package ga;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import convertTestsTojUnit.JUnitTestGenerator;

import crossover.Crossover;
import executor.ReflectionExecutor;
import  initialization.PopulationInitializer;
import instrumentation.TrackerCoverage;
import localSearch.LocalSearch;
import model.CUT;
import model.TestClusterBuilder;
import model.Filter;
import mutation.Mutation;
import mutation.MutationFactory;
import readWriteFieldsRelations.MethodDependences;
import selection.Selection;
import util.GAConfig;
import util.GAOperationsConfig;
import util.Log;
import util.Randomness;


public class GeneticAlgorithm {
	private static final int     TEST_SIZE_GOAL    = GAConfig.TestCaseSize;
	private static final int     POPULATION_SIZE       = GAConfig.PopulationSize;
	private static final int     elitismNum=(int)(((double)GAConfig.PopulationSize/(double)100) * GAConfig.elitismNumBest);
	private    TrackerCoverage tracker=  TrackerCoverage.getTracker();
	private  static float individualFitness=0;
	private  static final Mutation ChangeValueStrategy = GAOperationsConfig.getMutationChangeValueStrategy();
	private  static final Mutation MethodInsertStrategy = GAOperationsConfig.getMutationMethodInsertStrategy();
	private  static final Mutation MethodDeleteStrategy = GAOperationsConfig.getMutationMethodDeleteStrategy();
	private  static final Selection parentSelector = GAOperationsConfig.getParentSelector();
	private  static final Crossover crossoverStrategy = GAOperationsConfig.getCrossoverStrategy();
	private  static final PopulationInitializer populationInitializer = GAOperationsConfig.getPopulationInitializer();
	private  static final LocalSearch HillClimbing = GAOperationsConfig.getLocalSearch();
	private MethodDependences recommender = null;
	private static List<Integer> branchs ;

	private GAInfo     gaInfo     = new GAInfo();
	private  long startTime;
	private  long elapsed;
	private static  long TestTimeout;
	public GeneticAlgorithm() {	
		ChangeValueStrategy.setExtraInfo(this.gaInfo);
		MethodInsertStrategy.setExtraInfo(this.gaInfo);	
		MethodDeleteStrategy.setExtraInfo(this.gaInfo);	
		 parentSelector.setExtraInfo(this.gaInfo);
		populationInitializer.setExtraInfo(this.gaInfo);

	}
	
	
	private void initialize(TestClusterBuilder tCluster) throws IOException, ClassNotFoundException{
		HillClimbing.setExtraInfo(gaInfo);



		this.gaInfo.setTestCluster(tCluster);
		gaInfo.setMapBranchCondition(HillClimbing.readFileWGZ(this.gaInfo.getTestCluster()));

			if (GAConfig.SaveData) 
				Log.log("\n"+tCluster.getClassToTest().keySet().toString());

			//the method recommender
			 if(GAConfig.UseReadWrietRelation){
				 System.out.println( "Building up method dependence model for diversifying...");
				 Set<Class<?>> cluster = new LinkedHashSet<Class<?>>();
					for(String cName:tCluster.getCluster().keySet()){
						Class<?> cls=tCluster.getCluster().get(cName).getClazz();
						if(Filter.isPrimitiveOrStringType(cls))continue;
						if(cls.getName().startsWith("java.")) continue;
						if(cls.getName().startsWith("sun.")) continue;
						if(cls.getName().contains("$")) continue;
						if(!Filter.canUse(cls))continue;
						cluster.add(cls);
					}

					MethodDependences recommender = new MethodDependences(cluster);
					 recommender.buildDependence(tCluster.getStatements());
					 System.out.println("Finishing method dependence model building");
					 this.gaInfo.setMethodRelations(recommender);
			 }


	
		Individual[] population = populationInitializer.createPopulation();
		this.gaInfo.setPopulation(population);
		
		this.gaInfo.setGeneration(1);
		this.gaInfo.setPopulationFitness(0);
		branchs = new ArrayList<Integer>( this.gaInfo.getMapBranchCondition().keySet());  
		maximumBranchNumber=branchs.size();
		 System.out.println("Total target branchs: "+ branchs);
		 if (GAConfig.SaveData) 
			 Log.log("Total target branchs: "+branchs.size()+ " "+ branchs);
	}
	
	private void execute() throws IOException, ClassNotFoundException{
		int noMinutes=1;
		for ( int it = 1; (it<=10000000 && elapsed < TestTimeout) ; it++) {
			Individual[] population= this.gaInfo.getPopulation();
			//Calculate fitness of initial population
			int i=elitismNum;
			if(this.gaInfo.getGeneration()==1)
				i=0;

			for(;i<population.length;i++) {
				Individual individual= population[i];
				ReflectionExecutor.execute(this.gaInfo,individual);
				float []individualFitness= tracker.calculateFitness(individual,gaInfo.getMapBranchCondition().keySet());
				individual.setFitness(individualFitness[0]);
				individual.setBranchDistance(individualFitness[1]);
				
				tracker.reset();			
			}
			List<Integer> branchs = new ArrayList<Integer>( gaInfo.getMapBranchCondition().keySet());  

			float totalCoveredBranches=tracker.getTotalCoveredBranch(branchs);
		
				

			 System.out.print("elapsed time: "+ elapsed + " of "+TestTimeout);
			System.out.println("\t Total Branch Covered= ( "+this.gaInfo.getPopulationFitness()+ " , "+ totalCoveredBranches +" )");
			//
			 if(this.gaInfo.getPopulationFitness()<totalCoveredBranches){
				 	this.gaInfo.setPopulationFitness(totalCoveredBranches);
				 	this.gaInfo.setNoImprovementCount(0);
			 }
			 else
				 this.gaInfo.setNoImprovementCount(this.gaInfo.getNoImprovementCount() + 1);
			
		   // perform the HillClimbing
				if(this.gaInfo.getNoImprovementCount()>=3) {
					this.gaInfo.setNoImprovementCount(0);
					for (int localSearchNum = 0; (localSearchNum < GAConfig.localSearchNum) && (elapsed < TestTimeout); localSearchNum++) {
						Individual improvedIndividual =HillClimbing.execute();
						if(improvedIndividual!=null)
							population[population.length-(1+localSearchNum)]=improvedIndividual;
						elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);
					}
					}

				if(totalCoveredBranches>=maximumBranchNumber)
					break;

			// Generations ...
			this.evolve();
			elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);
			if((elapsed/60000)==noMinutes){
				 if (GAConfig.SaveData) Log.log("at " +  noMinutes +"  minute(s) Total Branch Covered= " + totalCoveredBranches );
				 noMinutes++;

			}

		}
	}
	private void evolve(){
		// Generations ...

		// perform the improvement
		
		// Create the offSprings
		Individual[] offspringPopulation= new Individual[POPULATION_SIZE];
		 for (int j=elitismNum; j < (POPULATION_SIZE)&&(elapsed < TestTimeout) ;) {
			  //Select two parents for reproduction
			 Individual parent1 = parentSelector.selectTwoParents();
			 Individual  parent2 = parentSelector.selectTwoParents();
			//Get the two children
			Individual[] offspring = crossoverStrategy.crossOver(parent1, parent2);
			//Mutate
			Mutation mutationOperator= MutationFactory.getMutationOperator();
			offspring[0]=mutationOperator.mutateIndividual(offspring[0]);
			mutationOperator= MutationFactory.getMutationOperator();
			offspring[1]=mutationOperator.mutateIndividual(offspring[1]);
			offspringPopulation[j++]=offspring[0];
            if(j<POPULATION_SIZE)
            		offspringPopulation[j++]=offspring[1];		
			elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);

		 }
		//elitis
		 Arrays.sort(this.gaInfo.getPopulation());
	       for(int i=0; i<elitismNum&& (elapsed < TestTimeout);i++){
	    	   offspringPopulation[i]= this.gaInfo.getPopulation()[i];//.clone();
	    	   elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);
	       }
		 this.gaInfo.setPopulation(offspringPopulation);
		 this.gaInfo.setGeneration(this.gaInfo.getGeneration() + 1);

	}
	
	
	
	public void start(TestClusterBuilder tCluster) throws IOException, ClassNotFoundException{
		startTime = Calendar.getInstance().getTimeInMillis();
		elapsed = Calendar.getInstance().getTimeInMillis() - startTime;
		gaInfo.setTestTimeOut(TestTimeout);
		gaInfo.setStartTime(startTime);


		this.initialize(tCluster);
		System.out.println("------------------");
		
		this.execute();

		 System.out.println ("\nDone testing at generation: " + gaInfo.getGeneration());
		 System.out.println("Maximum test time exceeded at " + elapsed );	
		 System.out.println(tracker.displayCoveredBranchs(branchs));
		 

		 if (GAConfig.SaveData) Log.log ("Done testing at generation: " + gaInfo.getGeneration());
		 if (GAConfig.SaveData) Log.log("Maximum test time exceeded at " + elapsed );
		 if (GAConfig.SaveData)	Log.log(tracker.displayCoveredBranchs(branchs));

		tracker.summarizeCoverage(branchs);
// Print out TestCase
		
		JUnitTestGenerator gen = new JUnitTestGenerator(GAConfig.getDirGeneratedTests(),gaInfo,tracker.branchCoveredByIndividual);
		gen.writeSuite();

	}
	public static long randomseed;// = (int) GAConfig.RANDOM_SEED;
	static int maximumBranchNumber=Integer.MAX_VALUE;

	private void setTestTimeout(int i){
		if(i!=0)
		 TestTimeout = 60 * 1000*i;
		else
			 TestTimeout = 60 * 500;	 
	}
	public static void writeLogFile(String cut,int i ){
		String logFilePath = null;
		String fileName = new SimpleDateFormat("MM_dd_yyyy_hhmm'.txt'")
			.format(new Date());
		
	String cName = GAConfig.classFilePath.substring(0,GAConfig.classFilePath.lastIndexOf("/") );
	cName = cName.substring(cName.lastIndexOf("/") +1);
	//cName=GAConfig.classFilePath;
	if(GAConfig.Seeding)
				logFilePath = "./logFiles/"+ cut+ "_MAMDA_Seeding_"+i+ "_PopNo_" + GAConfig.PopulationSize+ "_"+ fileName;
	else
		logFilePath = "./logFiles/"+ cut+ "_MAMDA_NoSeeding_"+i+ "_PopNo_" + GAConfig.PopulationSize+ "_"+ fileName;

		
	if (logFilePath != null) {
		Log.logConfig(logFilePath);
//		experiment_configure_options(TestTimeout);
		System.out.println("Log file place: "+ new File(logFilePath).getAbsolutePath());
		}
		Log.log("       SUT               : "+ GAConfig.classFilePath);
		Log.log ("      Population Size   : "+GAConfig.PopulationSize);
		Log.log ("      TestCase Size     : "+GAConfig.TestCaseSize);
		Log.log ("      Elitism       : "+GAConfig.elitismNumBest);
		Log.log ("      Timeout           : "+TestTimeout/60000 + " minutes  " + TestTimeout+" ms");
		Log.log ("");

	}
	private  void experiment_configure_options(long TestTimeout,int j) {
		Log.log("Start testing #..."+j);
		Log.log ("      Seed              : "+randomseed);
	}

	public static void main(String[] args) throws IOException,
			ClassNotFoundException {
		Set<Class<?>> allClasses = new LinkedHashSet<Class<?>>();
		// get class from file
		allClasses.addAll(TestClusterBuilder.readClassFromFile());

		System.out.println("There are " + allClasses.size()
				+ " classes as input");

		System.out.println(allClasses);
		List<TestClusterBuilder> TestClusterList = TestClusterBuilder
				.buildTestClusterList(allClasses);

		for (int i = 0; i <= 0; i += 6) {
			
			
			for(int times=1 ; times<=1;times++){

			GeneticAlgorithm algorithm = new GeneticAlgorithm();

			algorithm.setTestTimeout(i);

			// set the random generation seeds

			for (TestClusterBuilder tCluster : TestClusterList) {
				Map<String, Class<?>> classesToTest = tCluster.getClassToTest();
				CUT cut = tCluster.getCut();
				String clz = classesToTest.keySet().iterator().next();
				System.out.println("Class " + clz + " is being Tested");

				// if (GAConfig.SaveData)
				// Log.log("Class " + clz + " is being Tested");
				// System.out.println(classesToTest.keySet().toString());
				if (cut.getMethods().length == 0) {
					System.out.println("\tThere are no methods to test!");
					if (GAConfig.SaveData) {
						writeLogFile(clz, i);
						Log.log("\tThere are no methods to test!\n");
						Log.log.close();
					}

					continue;
				}
				if (GAConfig.SaveData)
					writeLogFile(clz, i);
				if (GAConfig.SaveData)
					Log.log("Class " + clz + " is being Tested");

				for (int j = 1; j <=1; j += 1) {
					System.out.println("Genetic Algorithm Start testing #..."
							+ j + " CUT is " + clz);

					randomseed = System.currentTimeMillis();
					Randomness.reset(randomseed);

					if (GAConfig.SaveData)
						algorithm.experiment_configure_options(TestTimeout, j);

					algorithm.start(tCluster);
					algorithm.tracker.setTrackerToNull();
					algorithm = new GeneticAlgorithm();

				}
				if (GAConfig.SaveData)
					Log.log.close();

			}
		}
		}
		System.exit(0);
}
}